/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include <baja/common.hpp>
#include <baja/win32/details/com_base.hpp>

#include <Windows.h>
#include <Objidl.h>

namespace baja { namespace win32 { namespace details {

struct win32_stream_wrapper : public com_base<IStream>
{
private:

    std::shared_ptr<istream> _stream;

public:
    win32_stream_wrapper(const std::shared_ptr<istream>& stream) : _stream(stream) {}

    virtual ~win32_stream_wrapper() {}

    static IStream* create(std::shared_ptr<istream>& stream)
    {
        return reinterpret_cast<IStream*>(new win32_stream_wrapper(stream));
    }

    HRESULT __stdcall
    Read(
        __out_bcount(numBytesToRead) VOID* buffer, 
        __in ULONG numBytesToRead, 
        __out_opt ULONG* numBytesRead
        )
    {
        HRESULT hr = S_OK;

        uint32 bytesToRead = static_cast<uint32>(numBytesToRead);

        if (numBytesRead)
            *numBytesRead = 0;

        try
        {
            uint32 bytesRead = _stream->read(buffer, bytesToRead);

            if (bytesRead < bytesToRead)
            {
                hr = S_FALSE;
            }

            if (numBytesRead)
                *numBytesRead = static_cast<LONG>(bytesRead);
        }
        catch (...)
        {
            hr = E_FAIL;
        }

        return hr;
    }

    HRESULT __stdcall
    Write(
        __out_bcount(numBytesToWrite) const VOID* buffer, 
        __in ULONG numBytesToWrite, 
        __out_opt ULONG* numBytesWritten
        )
    {
        HRESULT hr = S_OK;

        uint32 bytesToWrite = static_cast<uint32>(numBytesToWrite);

        if (numBytesWritten)
            *numBytesWritten = 0;

        try
        {
            uint32 bytesWritten = _stream->write(buffer, bytesToWrite);
            *numBytesWritten = static_cast<LONG>(bytesWritten);
        }
        catch (...)
        {
            hr = E_FAIL;
        }

        return hr;
    }

    HRESULT __stdcall
    SetSize(
        __in ULARGE_INTEGER newSize
        )
    {
        HRESULT hr = S_OK;
        
        if (newSize.HighPart > 0)
        {
            return E_INVALIDARG;
        }

        uint32 size = static_cast<uint32>(newSize.LowPart);

        try
        {
            _stream->setSize(size);
        }
        catch (...)
        {
            hr = E_FAIL;
        }

        return hr;
    }

    HRESULT __stdcall
    Seek(
        __in LARGE_INTEGER displacementFromOrigin, 
        __in DWORD origin, 
        __out_opt ULARGE_INTEGER* newPosition
        )
    {
        HRESULT hr = S_OK;
        
        if (displacementFromOrigin.HighPart > 0)
        {
            return E_INVALIDARG;
        }

        int32 offset = static_cast<int32>(displacementFromOrigin.LowPart);

        stream_origin o;

        if (origin == STREAM_SEEK_SET)
        {
            o = stream_origin::begin;
        }
        else if (origin == STREAM_SEEK_CUR)
        {
            o = stream_origin::current;
        }
        else if (origin == STREAM_SEEK_END)
        {
            o = stream_origin::end;
        }
        else
        {
            return E_INVALIDARG;
        }
        try
        {
            uint32 position = _stream->seek(offset, o);
            newPosition->HighPart = 0;
            newPosition->LowPart = static_cast<DWORD>(position);
        }
        catch (...)
        {
            hr = E_FAIL;
        }

        return hr;
    }

    HRESULT __stdcall
    Stat(
        __inout STATSTG* stats, 
        __in DWORD statFlag
        )
    {
        HRESULT hr = S_OK;

        if (stats == NULL)
        {
            return STG_E_INVALIDPOINTER;
        }
        if ((statFlag != 0) && (statFlag != STATFLAG_NONAME))
        {
            return STG_E_INVALIDFLAG;
        }

        ZeroMemory(stats, sizeof(STATSTG));
        
        // TEST: HACK
        if (SUCCEEDED(hr))
        {
            stats->pwcsName = NULL;
            stats->type = STGTY_STREAM;
            stats->cbSize.LowPart  = _stream->getSize();
            stats->cbSize.HighPart = 0;
            stats->grfMode = STGM_READ;
        }

        return hr;
    }

    HRESULT __stdcall
    Commit(
        __in DWORD commitFlags
        )
    {
        HRESULT hr = S_OK;
        
        try
        {
            _stream->commit(static_cast<DWORD>(commitFlags));
        }
        catch (...)
        {
            hr = E_FAIL;
        }

        return hr;
    }

    HRESULT _stdcall
    Revert(void) 
    {
        HRESULT hr = S_OK;
        
        try
        {
            _stream->revert();
        }
        catch (...)
        {
            hr = E_FAIL;
        }

        return hr;
    }

    HRESULT _stdcall
    LockRegion(
        ULARGE_INTEGER startOffset,
        ULARGE_INTEGER numBytesToLock,
        DWORD lockType
        )
    {
        HRESULT hr = S_OK;
        
        if (startOffset.HighPart > 0)
        {
            return E_INVALIDARG;
        }

        uint32 offset = static_cast<uint32>(startOffset.LowPart);

        if (numBytesToLock.HighPart > 0)
        {
            return E_INVALIDARG;
        }

        uint32 bytesToLock = static_cast<uint32>(numBytesToLock.LowPart);

        try
        {
            _stream->lockRegion(offset, bytesToLock, static_cast<uint32>(lockType));
        }
        catch (...)
        {
            hr = E_FAIL;
        }

        return hr;
    }

    HRESULT _stdcall
    UnlockRegion(
        ULARGE_INTEGER startOffset,
        ULARGE_INTEGER numBytesToUnlock,
        DWORD lockType
        )
    {
        HRESULT hr = S_OK;
        
        if (startOffset.HighPart > 0)
        {
            return E_INVALIDARG;
        }

        uint32 offset = static_cast<uint32>(startOffset.LowPart);

        if (numBytesToUnlock.HighPart > 0)
        {
            return E_INVALIDARG;
        }

        uint32 bytesToLock = static_cast<uint32>(numBytesToUnlock.LowPart);

        try
        {
            _stream->unlockRegion(offset, bytesToLock, static_cast<uint32>(lockType));
        }
        catch (...)
        {
            hr = E_FAIL;
        }

        return hr;
    }

    HRESULT _stdcall
    Clone(
        IStream** stream
        )
    {
        return E_NOTIMPL;
    }

    HRESULT _stdcall
    CopyTo(
        IStream* destStream,
        ULARGE_INTEGER numBytesToCopy,
        ULARGE_INTEGER* numBytesRead,
        ULARGE_INTEGER* numBytesWritten
        )
    {
        return E_NOTIMPL;
    }

protected:

    void internalQueryInterface(const IID& iid, void** ppv)
    {
        *ppv = NULL;

        if (__uuidof(IStream) == iid)
        {
            *ppv = static_cast<IStream*>(this);
        }
        else if (__uuidof(ISequentialStream) == iid)
        {
            *ppv = static_cast<ISequentialStream*>(this);
        }
    }
};

}}} // namespace baja.win32.details